package api

import (
	"context"
	"encoding/json"
	"encoding/xml"
	"net/url"
	"time"

	"zhonghui/console/models"
	"zhonghui/console/service"
	"zhonghui/console/structs"
	"zhonghui/console/warehouse/cfgx"
	"zhonghui/console/warehouse/dbx/db_zh_kernel"
	"zhonghui/console/warehouse/logx"
	"zhonghui/console/warehouse/modelx/model_zh_kernel"
	"zhonghui/console/warehouse/mqx"
	"zhonghui/console/wxutil"

	"github.com/gin-gonic/gin"
)

type DataCallbackReq struct {
	MsgSignature string `form:"msg_signature"`
	Timestamp    string `form:"timestamp"`
	Nonce        string `form:"nonce"`
	Echostr      string `form:"echostr"`
}

type InstructionCallbackReq struct {
	MsgSignature string `form:"msg_signature"`
	Timestamp    string `form:"timestamp"`
	Nonce        string `form:"nonce"`
	Echostr      string `form:"echostr"`
	ToUserName   string `xml:"ToUserName"`
	Encrypt      string `xml:"Encrypt"`
	AgentID      string `xml:"AgentID"`
}

type InstructionBody struct {
	ToUserName string `json:"tousername"`
	Encrypt    string `json:"encrypt"`
	AgentID    string `json:"agentid"`
}

type MsgContent struct {
	XMLName        xml.Name `xml:"xml"`
	SuiteId        string   `xml:"SuiteId"`
	InfoType       string   `xml:"InfoType"`
	AuthCode       string   `xml:"AuthCode"`
	ChangeType     string   `xml:"ChangeType"`
	UserID         string   `xml:"UserID"`
	ExternalUserID string   `xml:"ExternalUserID"`
	TimeStamp      string   `xml:"TimeStamp"`
	SuiteTicket    string   `xml:"SuiteTicket"`
	ToUsername     string   `xml:"ToUserName"`
	FromUsername   string   `xml:"FromUserName"`
	CreateTime     uint32   `xml:"CreateTime"`
	MsgType        string   `xml:"MsgType"`
	TagType        string   `xml:"TagType"`
	TAGID          string   `xml:"Id"`
	FailReason     string   `xml:"FailReason"`
	Content        string   `xml:"Content"`
	Source         string   `xml:"Source"`
	Msgid          uint64   `xml:"MsgId"`
	Agentid        uint32   `xml:"AgentId"`
	AuthCorpId     string   `xml:"AuthCorpId"`
	WelcomeCode    string   `xml:"WelcomeCode"`
	State          string   `xml:"State"`
}

func (server *Server) DataCallback(c *gin.Context) {
	req := DataCallbackReq{}
	if err := c.ShouldBindQuery(&req); err != nil {
		logx.Zap().Errorw("DataCallback ShouldBindQuery", "err", err)
		c.String(200, "parameter error")
		return
	}

	cfg := cfgx.Cfg().GetStringMapString("wx")
	wept := wxutil.NewWXBizMsgCrypt(cfg["token"],
		cfg["aes_key"],
		cfg["receive_id"],
		wxutil.JsonType)

	raw, err := url.PathUnescape(req.Echostr)
	if err != nil {
		logx.Zap().Errorw("DataCallback PathUnescape", "err", err)
		c.String(200, "echo str error")
		return
	}

	echoStr, cryptErr := wept.VerifyURL(req.MsgSignature, req.Timestamp, req.Nonce, raw)
	if cryptErr != nil {
		logx.Zap().Errorw("DataCallback VerifyURL", "err", cryptErr)
		c.String(200, "signature err")
		return
	}

	c.String(200, string(echoStr))
}

func (server *Server) InstructionCallback(c *gin.Context) {
	req := InstructionCallbackReq{}
	if err := c.ShouldBindQuery(&req); err != nil {
		logx.Zap().Errorw("InstructionCallback ShouldBindQuery", "err", err)
		c.String(200, "parameter error query")
		return
	}

	cfg := cfgx.Cfg().GetStringMapString("wx")
	wept := wxutil.NewWXBizMsgCrypt(cfg["token"],
		cfg["aes_key"], cfg["receive_id"],
		wxutil.JsonType)

	if req.Echostr != "" {
		raw, err := url.PathUnescape(req.Echostr)
		if err != nil {
			logx.Zap().Errorw("InstructionCallback PathUnescape", "err", err)
			c.String(200, "echo str error")
			return
		}
		echoStr, cryptErr := wept.VerifyURL(req.MsgSignature, req.Timestamp, req.Nonce, raw)
		if cryptErr != nil {
			logx.Zap().Errorw("InstructionCallback VerifyURL", "err", cryptErr)
			c.String(200, "signature err")
			return
		}
		c.String(200, string(echoStr))
		return
	}

	if err := c.ShouldBindXML(&req); err != nil {
		logx.Zap().Errorw("InstructionCallback ShouldBindXML", "err", err)
		c.String(200, "parameter error xml")
		return
	}

	reqData, err := json.Marshal(InstructionBody{
		ToUserName: req.ToUserName,
		Encrypt:    req.Encrypt,
		AgentID:    req.AgentID,
	})
	if err != nil {
		logx.Zap().Errorw("InstructionCallback Marshal", "err", err)
		c.String(200, "parameter error marshal")
		return
	}

	msg, cryptErr := wept.DecryptMsg(req.MsgSignature, req.Timestamp, req.Nonce, reqData)
	if nil != cryptErr {
		logx.Zap().Errorw("InstructionCallback DecryptMsg", "err", cryptErr)
		c.String(200, "signature err")
		return
	}

	server.EventProcess(c, msg)

	c.String(200, "success")
}

func (server *Server) EventProcess(c *gin.Context, msg []byte) {
	// https://developer.work.weixin.qq.com/document/path/92277 事件定义
	var msgContent MsgContent
	err := xml.Unmarshal(msg, &msgContent)
	if nil != err {
		logx.Zap().Errorw("EventProcess Unmarshal", "msg", msg, "msgContent", msgContent, "err", err)
		return
	}
	logx.Zap().Infow("EventProcess", "msg", msg, "msgContent", msgContent)

	mqClient, err := mqx.NewNsqProducer()
	if err != nil {
		logx.Zap().Errorw("EventProcess GetProducer", "err", err)
	}

	switch msgContent.InfoType {
	case "suite_ticket":
		if err := wxutil.SetSuiteTicket(msgContent.SuiteId, msgContent.SuiteTicket); err != nil {
			logx.Zap().Errorw("EventProcess SetSuiteTicket", "err", err)
			return
		}
	case "create_auth":

		err = mqClient.Publish("create_auth", msg)
		if err != nil {
			logx.Zap().Errorw("EventProcess Publish", "err", err)
		}

		suiteAccessToken, err := wxutil.GetSuiteAccessToken(msgContent.SuiteId)
		if err != nil {
			logx.ZapTrace(c).Errorw("获取suiteAccessToken失败", "err", err)
			return
		}

		permanentCodeInfo, err := service.GetPermanentCode(msgContent.AuthCode, suiteAccessToken)
		if err != nil {
			logx.ZapTrace(c).Errorw("获取permanentCodeInfo失败", "err", err)
			return
		}

		_, err = server.store.CreateCorp(context.Background(), models.CreateCorpParams{
			Corpid:        permanentCodeInfo.AuthCorpInfo.Corpid,
			AccessToken:   permanentCodeInfo.AccessToken,
			ExpireAt:      time.Now().Add(time.Duration(permanentCodeInfo.ExpiresIn) * time.Second),
			CorpName:      permanentCodeInfo.AuthCorpInfo.CorpName,
			CorpFullName:  permanentCodeInfo.AuthCorpInfo.CorpFullName,
			PermanentCode: permanentCodeInfo.PermanentCode,
			AuthInfo:      json.RawMessage(permanentCodeInfo.AuthInfo),
			AuthUserInfo:  json.RawMessage(permanentCodeInfo.AuthUserInfo),
		})
		if err != nil {
			logx.ZapTrace(c).Errorw("入库失败 CreateCorp", "err", err)
			return
		}

		// 更新
		err = wxutil.SetAppAccessToken(permanentCodeInfo.AuthCorpInfo.Corpid, permanentCodeInfo.AccessToken)
		if err != nil {
			logx.ZapTrace(c).Errorw("设置appAccessToken失败", "err", err)
			return
		}

	case "cancel_auth":
		_, err := server.store.UpdateCorpAuthStatus(context.Background(), msgContent.AuthCorpId)
		if err != nil {
			logx.ZapTrace(c).Errorw("更新企业授权状态失败", "err", err)
			return
		}
	case "change_external_contact":
		switch msgContent.ChangeType {
		case "add_external_contact":

			accessToken, err := wxutil.GetAppAccessToken(msgContent.AuthCorpId)
			if err != nil {
				logx.ZapTrace(c).Errorw("获取accessToken失败", "err", err)
				return
			}

			logx.Zap().Infow("EventProcess", "accessToken", accessToken)

			// 埋点
			{
				peID := getPendingId(c, msgContent.ExternalUserID, accessToken)
				sms := model_zh_kernel.SysSmsRecord{}
				err = db_zh_kernel.Client().Where("external_userid = ? or (pending_id = ? and pending_id != '')", msgContent.ExternalUserID, peID).Take(&sms).Error
				logx.Zap().Infow("EventProcess GetSMSRecord", "sms", sms)
				if err != nil {
					logx.ZapTrace(c).Errorw("获取短信记录失败", "err", err)
				} else {
					service.ReportData(uint64(sms.TaskID), sms.Phone, structs.ReportKindWecom, "add_external_contact")
				}
			}

			// 创建客服详情
			{
				detail, err := service.GetCustomerDetail(accessToken, msgContent.ExternalUserID)
				if err != nil {
					logx.ZapTrace(c).Errorw("获取客户详情失败", "err", err)
					return
				}
				logx.Zap().Infow("EventProcess GetCustomerDetail", "detail", detail)
				err = server.store.CreateCustomer(context.Background(), models.CreateCustomerParams{
					Corpid:         msgContent.AuthCorpId,
					UserID:         msgContent.UserID,
					ExternalUserID: msgContent.ExternalUserID,
					Channel:        msgContent.State,
					Avatar:         detail.ExternalContact.Avatar,
					Nickname:       detail.ExternalContact.Name,
					Gender:         detail.ExternalContact.Gender,
					Category:       detail.ExternalContact.Type,
					Unionid:        detail.ExternalContact.Unionid,
					Status:         "add_external_contact",
				})
				if err != nil {
					logx.ZapTrace(c).Errorw("入库失败 CreateCustomer", "msg", msgContent, "err", err)
					return
				}
			}

			// 发送欢迎语
			{
				contactId, err := server.store.GetContactByCorpIdAndChannel(context.Background(), models.GetContactByCorpIdAndChannelParams{
					Corpid:  msgContent.AuthCorpId,
					Channel: msgContent.State,
				})
				if err != nil {
					logx.ZapTrace(c).Errorw("获取二维码配置失败", "msg", msgContent, "err", err)
					return
				}

				employeeId, err := server.store.GetEmployeeIdByContactId(context.Background(), contactId)
				if err != nil {
					logx.ZapTrace(c).Errorw("获取员工id失败", "contactId", contactId, "msg", msgContent, "err", err)
					return
				}

				content, err := server.store.GetWelcomeById(context.Background(), employeeId)
				if err != nil {
					logx.ZapTrace(c).Errorw("获取欢迎语失败", "employeeId", employeeId, "msg", msgContent, "err", err)
					return
				}
				if content != "" {
					resp, err := service.PushWelcomeCode(msgContent.WelcomeCode, content, accessToken)
					if err != nil {
						logx.ZapTrace(c).Errorw("推送欢迎语失败", "msg", msgContent, "resp", resp, "err", err)
						return
					}
					logx.Zap().Infow("EventProcess PushWelcomeCode", "resp", resp)
				}
			}

		case "del_follow_user", "del_external_contact": // 被外部联系人删除,成员删除外部联系人时
			err = server.store.UpdateCustomerStatus(context.Background(), models.UpdateCustomerStatusParams{
				Corpid:         msgContent.AuthCorpId,
				ExternalUserID: msgContent.ExternalUserID,
				Status:         msgContent.ChangeType,
			})
			if err != nil {
				logx.ZapTrace(c).Errorw("更新客户状态失败", "err", err)
				return
			}
		case "add_half_external_contact":
		case "edit_external_contact":
		case "transfer_fail":

		default:
		}
	case "change_external_tag": // 企业客户标签变更事件
		switch msgContent.ChangeType {
		case "delete":
			// TagType tag tag_group
		case "update":
		case "create":
		}
	case "change_external_chat": // 企业群变更事件
		switch msgContent.ChangeType {
		case "update":
		case "dismiss":
		case "create":
		}
	default:
		logx.Zap().Errorw("IgnoreEvent", "msgContent", msgContent)
	}

}

func getPendingId(c *gin.Context, externalUserId, accessToken string) string {
	ret, err := service.ExternalToPending(externalUserId, accessToken)
	if err != nil {
		logx.ZapTrace(c).Errorw("获取客户pending失败", "err", err)
		return ""
	}
	logx.Zap().Infow("getPendingId", "ret", ret)
	if ret.Errcode != 0 {
		logx.ZapTrace(c).Errorw("获取客户pending失败", "err", ret.Errmsg)
		return ""
	}
	if len(ret.Result) > 0 {
		logx.ZapTrace(c).Infow("获取客户pending成功", "pending", ret.Result)
		return ret.Result[0].PendingId
	}
	return ""
}
